package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponScopeVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enums.CouponStatus;
import com.tianji.promotion.enums.ObtainType;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.tianji.promotion.constants.PromotionConstants.COUPON_CACHE_KEY_PREFIX;
import static com.tianji.promotion.enums.CouponStatus.*;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author zhongweiwei
 * @since 2023-06-03
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    private final ICouponScopeService couponScopeService;
    private final IExchangeCodeService codeService;
    private final IUserCouponService userCouponService;
    private final StringRedisTemplate redisTemplate;

    /**
     * 新增优惠卷
     *
     * @param dto
     */
    @Override
    public void saveCoupon(CouponFormDTO dto) {
        // 1.保存优惠券
        // 1.1.转PO
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        // 1.2.保存
        this.save(coupon);
        // 没有范围限定
        Boolean specific = coupon.getSpecific();
        if (!specific) {
            return;

        }
        Long couponId = coupon.getId();
        List<Long> scopes = dto.getScopes();
        if (CollUtils.isEmpty(scopes)) {
            throw new RuntimeException("限定范围不能为空");
        }
        List<CouponScope> list = scopes.stream().map(bizId -> new CouponScope()
                        .setBizId(bizId).setCouponId(couponId))
                .collect(Collectors.toList());
        couponScopeService.saveBatch(list);

        // 2.保存限定范围
        // 2.1.转换PO
        // 2.2.保存
    }

    /**
     * 分页查询优惠卷
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<CouponPageVO> pageQueryCoupon(CouponQuery query) {
        //1-构建分页查询条件
        String name = query.getName();
        Integer type = query.getType();
        Integer status = query.getStatus();
        Page<Coupon> page = lambdaQuery()
                .like(name != null, Coupon::getName, name)
                .eq(type != null, Coupon::getDiscountType, type)
                .eq(status != null, Coupon::getStatus, status)
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        //2-取出结果集并做健壮性判断
        List<Coupon> records = page.getRecords();
        if (BeanUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        //3-处理vo
        List<CouponPageVO> vos = BeanUtils.copyList(records, CouponPageVO.class);

        //4-返回结果
        return PageDTO.of(page, vos);
    }

    /**
     * 发放优惠卷接口
     *
     * @param issueFormDTO
     */
    @Override
    public void issueCoupon(CouponIssueFormDTO issueFormDTO) {
        //1-获取优惠卷
        Coupon coupon = getById(issueFormDTO.getId());
        if (coupon == null) {
            throw new RuntimeException("优惠卷不存在");
        }
        //2-判断优惠卷的状态是否为暂停或者是待发放
        CouponStatus status = coupon.getStatus();
        if (status != CouponStatus.DRAFT && status != CouponStatus.PAUSE) {
            throw new RuntimeException("优惠卷状态错误");

        }
        LocalDateTime beginTime = issueFormDTO.getIssueBeginTime();
        //3-判断优惠卷是否为立刻发放
        LocalDateTime now = LocalDateTime.now();
        boolean b = beginTime == null || !beginTime.isAfter(now);
        //4-属性copy
        Coupon copyBean = BeanUtils.copyBean(issueFormDTO, Coupon.class);
        if (b) {
            copyBean.setStatus(CouponStatus.ISSUING);
            copyBean.setIssueBeginTime(now);
        } else {
            copyBean.setStatus(CouponStatus.UN_ISSUE);
        }
        updateById(copyBean);
        // TODO
        //生成兑换码
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT) {
            coupon.setIssueEndTime(coupon.getIssueEndTime());
            codeService.generateCode(coupon);
        }


    }

    /**
     * 修改优惠卷
     *
     * @param dto
     */
    @Override
    public void recomposeCouponById(CouponFormDTO dto) {
        Coupon coupon = getById(dto.getId());
        if (coupon == null) {
            throw new RuntimeException("优惠卷无信息");
        }
        CouponStatus status = coupon.getStatus();
        if (status != CouponStatus.DRAFT) {
            throw new RuntimeException("优惠卷状态异常");
        }
        Coupon copyBean = BeanUtils.copyBean(coupon, Coupon.class);
        updateById(copyBean);

    }

    /**
     * 删除优惠卷
     *
     * @param id
     */
    @Override
    public void deleteCouponById(Long id) {
        //只有处于待发放状态的优惠卷是可以修改的
//        Coupon coupon = getById(id);
//        CouponStatus status = coupon.getStatus();
//        if (status != CouponStatus.DRAFT) {
//            throw new RuntimeException("优惠卷状态异常,删除失败");
//        }
//        coupon.setId(id);
//        removeById(coupon);
        boolean success = remove(new LambdaQueryWrapper<Coupon>()
                .eq(Coupon::getId, id)
                .eq(Coupon::getStatus, CouponStatus.DRAFT));
        if (!success) {
            throw new RuntimeException("优惠卷状态异常，删除失败");

        }
    }

    /**
     * 根据id查询优惠卷详细信息接口
     *
     * @param id
     * @return
     */
    @Override
    public CouponDetailVO inquiriesById(Long id) {
        //1-获取订单
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new RuntimeException("优惠卷不存在查询失败");
        }
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        Boolean specific = coupon.getSpecific();
        //判断是否有使用范围
        if (!specific) {
            //，没有则直接返回vo
            return vo;
        }
        //有则继续查询vo所需字段
        List<CouponScopeVO> scopes = couponScopeService.queryByCouponId(coupon.getId());
        vo.setScopes(scopes);

        return vo;
    }

    /**
     * 根据id查询用于回显
     *
     * @param id
     * @return
     */
    @Override
    public CouponDetailVO getByCouponId(Long id) {
        //1-获取订单
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new RuntimeException("优惠卷不存在查询失败");
        }
        CouponDetailVO vo = BeanUtils.copyBean(coupon, CouponDetailVO.class);
        return vo;

    }

    /**
     * 暂停发放优惠卷
     *
     * @param id
     */
    @Override
    public void changeStatusById(Long id) {
        // 1.查询旧优惠券
        Coupon coupon = getById(id);
        if (coupon == null) {
            throw new BizIllegalException("优惠券不存在！");
        }

        // 2.当前券状态必须是未开始或进行中
        CouponStatus status = coupon.getStatus();
        if (status != UN_ISSUE && status != ISSUING) {
            // 状态错误，直接结束
            return;
        }

        // 3.更新状态
        boolean success = lambdaUpdate()
                .set(Coupon::getStatus, PAUSE.getValue())
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, UN_ISSUE.getValue(), ISSUING.getValue())
                .update();
        if (!success) {
            // 可能是重复更新，结束
            return;
        }

        // 4.删除优惠券缓存
        redisTemplate.delete(COUPON_CACHE_KEY_PREFIX + id);
    }

    /**
     * 查询发放中的优惠卷
     *
     * @return
     */
    @Override
    public List<CouponVO> queryAllCoupons() {
        //1-查询到发放中的优惠卷
        List<Coupon> coupons = lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        //1.1-收取优惠卷的id
        List<Long> cids = coupons.stream().map(Coupon::getId).collect(Collectors.toList());
        //查询用户的所有的优惠卷
        List<UserCoupon> userCoupons = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, cids)
                .list();
        //用户可以领取的优惠卷的数量
        Map<Long, Long> issuedMap = userCoupons.stream().collect(Collectors.groupingBy(
                UserCoupon::getCouponId,
                Collectors.counting()
        ));
        //用户可以使用的优惠卷数量
        Map<Long, Long> usedMap = userCoupons.stream()
                .filter(uc -> uc.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(
                        UserCoupon::getCouponId,
                        Collectors.counting()
                ));


        //2-准备vo
        List<CouponVO> list = new ArrayList<>(coupons.size());
        for (Coupon c : coupons) {
            CouponVO couponVO = BeanUtils.copyBean(c, CouponVO.class);
            list.add(couponVO);
            //校验当前优惠卷是否可以领取:领取数量
            couponVO.setAvailable(
                    c.getIssueNum() < c.getTotalNum()
                            && issuedMap.getOrDefault(c.getId(), 0L) < c.getUserLimit());

            //判断当前优惠卷是否可以使用
            couponVO.setReceived(
                    usedMap.getOrDefault(c.getId(), 0L) > 0
            );


        }


        return list;
    }
}
